/**
 * 
 */
package simulation;

import java.util.LinkedList;
import java.util.Random;

import common.TupleXYZ;

/**
 * @author vladimir
 */
public class Interval {
	double B = -1;
	public double length = 0;// length of our interval
	public LinkedList<TupleXYZ> clusters = new LinkedList<TupleXYZ>();// will
	// save
	// Tuples with
	// ends and
	// value on it
	Random rand = new Random();

	public Interval(double B, double T) {
		this.B = B;
		this.length = 1 / T;

		genInitialCuts();
	}

	// generates initial cuts
	public void genInitialCuts() {
		// let's say that there are no cuts, and all interval will be like one
		// cluster.
		int newValue = 2 * rand.nextInt(2) - 1;
		this.clusters.add(new TupleXYZ(0, 0, newValue));
	}

	/*
	 * returns Tuple with potential new cluster: newCluster.x - lower cut
	 * newcluster.y - upper cut newcluster.z - index of the cluster we are
	 * looking at
	 */
	public TupleXYZ newCluster() {
		if (this.B == 0) {
			return new TupleXYZ(0, 0, 0);
		} else {
			double tau = rand.nextFloat() * this.length;
			double newUp = tau - 1 / this.B * Math.log(rand.nextFloat());
			double newDown = tau + 1 / this.B * Math.log(rand.nextFloat());
			return newCluster(newDown, newUp, tau);
		}
	}

	public TupleXYZ newCluster(double newDown, double newUp, double tau) {
		// check input parameters
		// if (newDown > tau) {
		// System.err.println("We are in newCluster \n"
		// + " newDown  should be < tau \n" + "tau - " + tau
		// + "\n newDown -" + newDown);
		// System.exit(0);
		// }
		// if (newUp < tau) {
		// System.err.println("We are in newCluster \n"
		// + " newUp  should be > tau \n" + "tau - " + tau
		// + "\n newUp -" + newUp);
		// System.exit(0);
		// }

		if (this.clusters.size() == 1) {// no cuts
			if (newUp - newDown > this.length) {// newcluster is whole cluster
				return new TupleXYZ(0, 0, 0);
			} else {
				if (newUp > this.length) {
					newUp = newUp - this.length;
				} else if (newDown < 0) {
					newDown = newDown + this.length;
				}
				return new TupleXYZ(newDown, newUp, 0);
			}

		} else {// we have cuts
			int clusterIndex = index(tau);
			// System.out.println("tauIndex : " + clusterIndex);
			// System.out.println("tau : " + tau);
			// System.out.println("length : " + this.length);
			// print();
			TupleXYZ cluster = this.clusters.get(clusterIndex);
			if (clusterIndex == 0) {// we are on the boundary
				if (tau > cluster.x) {
					if (newDown < cluster.x) {
						newDown = cluster.x;
					}
					if (newUp > this.length) {
						newUp = newUp - this.length;
						if (newUp > cluster.y) {
							newUp = cluster.y;
						}
					}
				} else {
					if (newUp > cluster.y) {
						newUp = cluster.y;
					}
					if (newDown < 0) {
						newDown = newDown + this.length;
						if (newDown < cluster.x) {
							newDown = cluster.x;
						}
					}
				}
			} else {// we are somewhere inside
				if (newUp > cluster.y) {
					newUp = cluster.y;
				}
				if (newDown < cluster.x) {
					newDown = cluster.x;
				}
			}
			return new TupleXYZ(newDown, newUp, clusterIndex);
		}
	}

	/*
	 * returns index of the cluster in which we have coordinate
	 */
	public int index(double coordinate) {
		// if (!inRange(coordinate)) {
		// print();
		// System.err
		// .println("In index. coordinate should be within length, but we have \n"
		// + "coordinate - "
		// + coordinate
		// + "\n"
		// + "while length of the interval is - "
		// + this.length);
		//
		// System.exit(0);
		// }
		if (coordinate == 0) {
			return 0;
		}
		if (this.clusters.size() == 1) {// no cuts on the cluster
			return 0;
		} else {// at least two cuts
				// Let's check boundary term
			TupleXYZ cluster = this.clusters.get(0);
			// if (cluster.x < cluster.y) {
			// System.err
			// .println("Wrong definition of the cluster with index zero");
			// System.exit(0);
			// }

			if ((coordinate < cluster.y) || (cluster.x < coordinate)) {
				return 0;
			}
			for (int i = 1; i < this.clusters.size(); i++) {
				cluster = this.clusters.get(i);
				if ((cluster.x < coordinate) && (coordinate < cluster.y)) {
					return i;
				}
			}
		}
		return -1;
	}

	/*
	 * returns value on the interval if no arguments returns whole value if
	 * there are 2 arguments will return part of the value
	 */
	public double value() {
		double result = 0;
		for (int z = 0; z < this.clusters.size(); z++) {
			result += segmentValue(z);
		}
		return result;

	}

	public double value(double lower, double upper) {
		if (lower < upper) {
			double result = 0;
			if (this.clusters.size() == 1) {
				TupleXYZ cluster = this.clusters.get(0);
				return (upper - lower) * cluster.z;
			}
			if (lower == 0) {
				System.err
						.println("lower should be less than upper, but we have: \n"
								+ "lower : " + lower + "upper : " + upper);
				print();
				System.exit(0);
			}
			if (upper == 0) {
				System.err
						.println("lower should be less than upper, but we have: \n"
								+ "lower : " + lower + "upper : " + upper);
				print();
				System.exit(0);
			}
			int lowInd = index(lower + 0.0000000001);
			int upInd = index(upper - 0.0000000001);

			if (lowInd == upInd) {
				TupleXYZ cluster = this.clusters.get(lowInd);
				if (lowInd != 0) {
					return cluster.z * (upper - lower);
				} else {
					if ((upper > cluster.x)
							&& (lower + 0.00000000001 > cluster.x)) {
						return cluster.z * (upper - lower);
					} else if ((lower < cluster.y)
							&& (upper - 0.000000001 < cluster.y)) {
						return cluster.z * (upper - lower);
					} else {
						result += cluster.z
								* (cluster.y - lower + upper - cluster.x);

						for (int i = 1; i < this.clusters.size(); i++) {
							// cluster = this.clusters.get(i);
							result += segmentValue(i);// cluster.z * (cluster.y
														// - cluster.x);
						}
						// System.out.println("We are in l > x, u < y");
						return result;
					}
				}

			} else {
				TupleXYZ cluster = this.clusters.get(upInd);
				result += cluster.z * (upper - cluster.x);

				if (upInd == 0) {
					upInd = this.clusters.size();
				}

				cluster = this.clusters.get(lowInd);
				result += cluster.z * (cluster.y - lower);

				for (int i = lowInd + 1; i < upInd; i++) {
					// cluster = this.clusters.get(i);
					result += segmentValue(i);// cluster.z * (cluster.y -
												// cluster.x);
				}
				return result;
			}
		} else if (lower == upper) {
			if (equal(lower, 0)) {
				return value();
			} else {
				System.err.println("value. something is wrong. \n" + "lower - "
						+ lower + " upper - " + upper
						+ " this.clusters.size - " + this.clusters.size());
				print();
				System.exit(0);
				return -1;
			}
		} else {
			return value() - value(upper, lower);
		}
	}

	/*
	 * changes cluster to a new value x - lower value for new cluster y - upper
	 * value for new cluster z - index
	 */
	public void set(TupleXYZ newCluster) {
		// if (!inRange(newCluster.x)) {
		// System.out.println("newCluster.x should be within range");
		// System.exit(0);
		// }
		// if (!inRange(newCluster.y)) {
		// System.out.println("newCluster.y should be within range");
		// System.exit(0);
		// }
		// if ((0 > newCluster.z) || (newCluster.z >= this.clusters.size())) {
		// System.out.println("newCLuster.z should be within range");
		// }
		//
		// if (this.clusters.size() == 0) {
		// System.out.println("Should be at least one cluster");
		// System.exit(0);
		// }
		int i = replace(newCluster);
		if (this.clusters.size() == 2) {// only two cuts and we try join it into
										// the same loop
										// join(0, 1);
			if (this.clusters.get(0).z == this.clusters.get(1).z) {
				int clustInd = this.clusters.get(0).z;
				this.clusters.remove();
				this.clusters.remove();
				this.clusters.add(new TupleXYZ(0, 0, clustInd));
			}

		} else if (this.clusters.size() == 1) {// should not join anything
		} else if (i == this.clusters.size() - 1) {
			join(this.clusters.size() - 1, this.clusters.size() - 2);
			join(this.clusters.size() - 1, 0);
		} else if (i == 0) {
			join(0, 1);
			join(this.clusters.size() - 1, 0);
		} else {
			join(i, i + 1);
			join(i, i - 1);
		}

	}

	/*
	 * function compares two boolean numbers up to some precision
	 */
	public static boolean equal(double x, double y) {
		if (Math.abs(x - y) < 0.00001) {
			return true;
		} else {
			return false;
		}

	}

	/*
	 * replaces old cluster with new one. Returns index of the new cluster
	 */
	public int replace(TupleXYZ newCluster) {
		// if (!inRange(newCluster.x)) {
		// System.out.println("newCLuster.x should be within range");
		// System.exit(0);
		// }
		// if (!inRange(newCluster.y)) {
		// System.out.println("newCLuster.y should be within range");
		// System.exit(0);
		// }
		// if ((newCluster.z < 0) || (newCluster.z >= this.clusters.size())) {
		// System.out.println("newCluster.z should be within range");
		// System.exit(0);
		// }

		if ((this.clusters.size() == 1) && (newCluster.z == 0)) {
			if ((newCluster.x == 0) && (newCluster.y == 0)) {
				this.clusters.get(0).z *= -1;
				return 0;
			} else {
				this.addCut(newCluster.x);
				this.addCut(newCluster.y);

				if (newCluster.x < newCluster.y) {
					this.clusters.get(1).z *= -1;
					return 1;
				} else {

					this.clusters.get(0).z *= -1;
					return 0;
				}
			}
		}

		TupleXYZ cluster = this.clusters.get(newCluster.z);

		if (!equal(newCluster.x, cluster.x)) {
			addCut(newCluster.x);
		}
		if (!equal(newCluster.y, cluster.y)) {
			addCut(newCluster.y);
		}

		if (newCluster.x < newCluster.y) {
			int resultIndex = index((newCluster.x + newCluster.y) / 2);
			this.clusters.get(resultIndex).z *= -1;
			return resultIndex;
		} else {
			this.clusters.get(0).z *= -1;
			return 0;
		}

	}

	/*
	 * joins two neighboring clusters into one if they have same value
	 */
	public boolean join(int a, int b) {
		// if (a == b) {
		// System.err
		// .println("We can't join 2 clusters with the same index - "
		// + a);
		// System.err.println("Total number of sites - "
		// + this.clusters.size());
		// System.exit(0);
		// }
		// if (this.clusters.size() == 0) {
		// System.err.println("Should be at least one cut\n" + "in 'join'");
		// System.exit(0);
		// }
		// if (this.clusters.size() == 1) {
		// print();
		// System.err.println("Should be more than one cluster in join");
		// System.exit(0);
		// }
		//
		// if ((a < 0) || (a >= this.clusters.size())) {
		// System.err.println("a should be within range");
		// System.exit(0);
		// }
		// if ((b < 0) || (b >= this.clusters.size())) {
		// System.err.println("b should be within range");
		// System.exit(0);
		// }
		// if (Math.abs(a - b) != 1) {
		// if (Math.abs(a - b) != this.clusters.size() - 1) {
		// System.err
		// .println("We join only neighboring clusters, but we have \n"
		// + "a - "
		// + a
		// + " b - "
		// + b
		// + " number of clusters - "
		// + this.clusters.size());
		// System.exit(0);
		// }
		// }

		if (this.clusters.get(a).z == this.clusters.get(b).z) {// we won't do
																// anything if
																// values on the
																// clusters are
																// different
			int clusterValue = this.clusters.get(a).z;
			if (this.clusters.size() == 2) {
				this.clusters.remove(1);
				this.clusters.remove(0);
				this.clusters.add(new TupleXYZ(0, 0, clusterValue));
				return true;
			} else {
				int lower = Math.min(a, b);
				int upper = Math.max(a, b);
				if ((upper == this.clusters.size() - 1) & (lower == 0)) {
					double newLower = this.clusters.get(upper).x;
					this.clusters.get(lower).x = newLower;
					this.clusters.remove(upper);
					return true;
				} else {
					double newLower = this.clusters.get(lower).x;
					// double newUpper = this.clusters.get(upper).y;

					this.clusters.get(upper).x = newLower;
					this.clusters.remove(lower);
					// if (this.clusters.size() == 1) {
					// System.out
					// .println("We can't remove last cluster if we want remove it\n"
					// + "in join");
					// System.exit(0);
					// return false;
					// }
				}
			}
		}
		return false;

	}

	/*
	 * adds cut on the given position
	 */
	public void addCut(double newCut) {
		// if (!inRange(newCut)) {
		// System.err.println("newCut should be within range");
		// System.exit(0);
		// }

		if ((this.clusters.size() == 1) && (this.clusters.get(0).x == 0)
				&& (this.clusters.get(0).y == 0)) {
			this.clusters.get(0).x = newCut;
			this.clusters.get(0).y = newCut;
		} else if ((this.clusters.size() == 1) && (this.clusters.get(0).x != 0)
				&& (this.clusters.get(0).y != 0)) {
			double tempNew = this.clusters.get(0).x;
			int clusterValue = this.clusters.get(0).z;
			if (newCut > tempNew) {
				this.clusters.get(0).x = newCut;
				this.clusters.addLast(new TupleXYZ(tempNew, newCut,
						clusterValue));
			} else {
				this.clusters.get(0).y = newCut;
				this.clusters.addLast(new TupleXYZ(newCut, tempNew,
						clusterValue));
			}
		} else {
			int i = index(newCut);
			TupleXYZ cluster = this.clusters.get(i);

			double oldDown = cluster.x;
			int clusterValue = cluster.z;
			cluster.x = newCut;

			if ((i == 0) && (newCut > oldDown)) {
				this.clusters.addLast(new TupleXYZ(oldDown, newCut,
						clusterValue));
			} else {
				this.clusters.add(i,
						new TupleXYZ(oldDown, newCut, clusterValue));
			}
		}
	}

	/*
	 * prints all intervals with index, beginning, end, value
	 */
	public void print() {
		for (TupleXYZ cluster : this.clusters) {
			cluster.println();
		}
	}

	// /*
	// * returns magnetisation on the interval
	// */
	// public double M() {
	// if (Math.abs(this.value()) > this.length) {
	// System.err.println("we can't have M bigger then 1");
	// }
	// return this.value() / this.length;
	// }

	/*
	 * returns number of cuts
	 */
	public int nCuts() {
		if (this.clusters.size() == 1) {
			return 0;
		} else {
			// if (this.clusters.size() % 2 != 0) {
			// System.err.println("We should have only even number of cuts \n"
			// + "But we have - " + this.clusters.size());
			// print();
			// System.exit(0);
			// }
			return this.clusters.size();

		}
	}

	/*
	 * set value of the interval
	 */
	public void setSpin(int index, int value) {
		this.clusters.get(index).z = value;
	}

	/*
	 * checks if coodrinate is within length of the interval
	 */
	public boolean inRange(double coordinate) {
		if ((coordinate < 0) || (coordinate > this.length)) {
			return false;
		} else {
			return true;
		}
	}

	/*
	 * returns value of the segment
	 */
	public double segmentValue(int segmentIndex) {
		TupleXYZ segment = this.clusters.get(segmentIndex);
		if (this.clusters.size() == 1) {
			// if ((this.clusters.get(0).x != 0) || (this.clusters.get(0).y !=
			// 0)) {
			// System.err.println("wrong ends of the cluster \n"
			// + "should be 0's but we have " + "\n x : "
			// + this.clusters.get(0).x + "" + "\n y : "
			// + this.clusters.get(0).y);
			// System.exit(0);
			// }
			return this.length * this.clusters.get(0).z;
		} else {
			if (segmentIndex == 0) {
				return segment.z * (this.length - segment.x + segment.y);
			} else {
				return segment.z * (segment.y - segment.x);
			}
		}
	}
}
